using System;
using System.Collections.Generic;
using System.Linq;

using Microsoft.VisualStudio;
using Microsoft.VisualStudio.Package;
using Microsoft.VisualStudio.TextManager.Interop;
using Microsoft.WowAddonStudio.Lua.LanguageService.Intellisense;
using Microsoft.WowAddonStudio.Lua.LanguageService.Support;
using Microsoft.WowAddonStudio.Lua.Model;

namespace Microsoft.WowAddonStudio.Lua.LanguageService.Model
{
    /// <summary>
    /// Provides the information for word completion and member selection to CodeSense in response to a
    /// parse request.
    /// </summary>
    public class LuaAuthoringScope : AuthoringScope
    {
        private static readonly DeclarationEqualityComparer<Declaration> declarationEqualityComparer = new DeclarationEqualityComparer<Declaration>(); 
        private static readonly DeclarationEqualityComparer<Function> functionEqualityComparer = new DeclarationEqualityComparer<Function>(); 

        private readonly List<IDeclarationProvider> declarationProviders = new List<IDeclarationProvider>();
        private readonly LuaLanguageService languageService;

        private readonly LuaParseRequest parseRequest;

        /// <summary>
        /// Initializes a new instance of the <see cref="LuaAuthoringScope"/> class.
        /// </summary>
        /// <param name="languageService">The language service.</param>
        /// <param name="parseRequest">The parse request.</param>
        public LuaAuthoringScope(LuaLanguageService languageService, LuaParseRequest parseRequest)
        {
            if (languageService == null)
                throw new ArgumentNullException("languageService");
            if (parseRequest == null)
                throw new ArgumentNullException("parseRequest");

            this.languageService = languageService;
            this.parseRequest = parseRequest;
        }

        /// <summary>
        /// Adds a declaration provider to the authoring scope.
        /// </summary>
        /// <param name="declarationProvider">The CodeSense provider to add.</param>
        public void AddProvider(IDeclarationProvider declarationProvider)
        {
            if (declarationProvider == null)
                throw new ArgumentNullException("declarationProvider");

            // Initialize declaration provider
            declarationProvider.Initialize(parseRequest);

            // Add to list of declaration providers
            declarationProviders.Add(declarationProvider);
        }

        /// <summary>
        /// Returns a list of declarations based on the specified reason for parsing.
        /// </summary>
        /// <param name="view">[in] An <see cref="T:Microsoft.VisualStudio.TextManager.Interop.IVsTextView"/> object that can be used to access the source.</param>
        /// <param name="line">[in] The line number where the parse operation started.</param>
        /// <param name="col">[in] The offset into the line where the parse operation started.</param>
        /// <param name="info">[in] A <see cref="T:Microsoft.VisualStudio.Package.TokenInfo"/> structure containing information about the token at the specified position.</param>
        /// <param name="reason">[in] The <see cref="T:Microsoft.VisualStudio.Package.ParseReason"/> value describing what kind of parse operation was completed.</param>
        /// <returns>
        /// If successful returns a <see cref="T:Microsoft.VisualStudio.Package.Declarations"/> object; otherwise, returns a null value.
        /// </returns>
        public override Declarations GetDeclarations(IVsTextView view, int line, int col, TokenInfo info, ParseReason reason)
        {
            // Get the declarations from all providers for the scope, Order by Name and project the declarations into Babel.Declarations
            Declaration[] declarations = declarationProviders.SelectMany(provider => provider.GetDeclarations()).OrderBy(declaration => declaration.Name).Distinct(declarationEqualityComparer).ToArray();
            if (declarations.Length > 0)
                return new LuaDeclarations(languageService, declarations);

            return null;
        }

        /// <summary>
        /// Returns a list of overloaded method signatures for a specified method name.
        /// </summary>
        /// <param name="line">[in] The line number where the parse for method signatures started.</param>
        /// <param name="col">[in] The offset into the line where the parse for method signatures started.</param>
        /// <param name="name">[in] The name of the method for which to get permutations.</param>
        /// <returns>
        /// If successful, returns a <see cref="T:Microsoft.VisualStudio.Package.Methods"/> object; otherwise, returns a null value.
        /// </returns>
        public override Methods GetMethods(int line, int col, string name)
        {
            Function[] functions = declarationProviders.SelectMany(provider => provider.GetMethods()).OrderBy(function => function.Name).Distinct(functionEqualityComparer).ToArray();
            if (functions.Length > 0)
                return new LuaMethods(functions);

            return null;
        }

        /// <summary>
        /// Returns a string to be used for a tool tip based on the specified location.
        /// </summary>
        /// <param name="line">[in] The line in the source to look at for a tool tip.</param>
        /// <param name="col">[in] An offset within the <paramref name="line"/> to look at for a tool tip.</param>
        /// <param name="span">A <see cref="T:Microsoft.VisualStudio.TextManager.Interop.TextSpan"/> that describes the area over which the cursor can hover before the tool tip is dismissed from view.</param>
        /// <returns>
        /// If successful, returns a string containing the text for the tool tip; otherwise, returns a null value.
        /// </returns>
        public override string GetDataTipText(int line, int col, out TextSpan span)
        {
            span = new TextSpan();

            DataTip dataTip = declarationProviders.Select(provider => provider.GetDataTip()).FirstOrDefault(dt => dt != null);
            if (dataTip != null)
            {
                span = dataTip.Location.ToTextSpan();
                return dataTip.Text;
            }

            return null;
        }
        /// <summary>
        /// Returns a URI (Universal Resource Identifier) based on the current location in the source and the specified command.
        /// </summary>
        /// <param name="cmd">[in] A value from the <see cref="T:Microsoft.VisualStudio.VSConstants.VSStd97CmdID"/> enumeration that determines what kind of destination URI must be returned. This is the command the user entered, typically from a context menu.</param>
        /// <param name="textView">[in] The <see cref="T:Microsoft.VisualStudio.TextManager.Interop.IVsTextView"/> object containing the text under the cursor.</param>
        /// <param name="line">[in] The line number containing the text under the cursor.</param>
        /// <param name="col">[in] The offset into the line containing the text under the cursor.</param>
        /// <param name="span">[out] A <see cref="T:Microsoft.VisualStudio.TextManager.Interop.TextSpan"/> object marking the selected text area for which the URI is determined.</param>
        /// <returns>
        /// If successful, returns a string containing the URI; otherwise, returns a null value.
        /// </returns>
        public override string Goto(VSConstants.VSStd97CmdID cmd, IVsTextView textView, int line, int col, out TextSpan span)
        {
            span = new TextSpan();

            DeclarationSource declarationSource = declarationProviders.Select(provider => provider.GetDeclarationSource()).FirstOrDefault(ds => ds != null);
            if (declarationSource != null)
            {
                span = new TextSpan
                       {
                           iStartLine = declarationSource.Location.Line,
                           iStartIndex = declarationSource.Location.Column,
                           iEndLine = declarationSource.Location.Line,
                           iEndIndex = declarationSource.Location.Column
                       };

                return declarationSource.Uri;
            }

            return null;
        }
    }
}